#pragma once

#include "sfc/core/chr.h"
#include "sfc/core/slice.h"

namespace sfc::str {

struct Str {
  slice::Slice<const char> _buf;

 public:
  [[sfc_inline]] Str() = default;

  [[sfc_inline]] Str(const char* s) noexcept : _buf{s, s == nullptr ? 0 : __builtin_strlen(s)} {}

  [[sfc_inline]] explicit Str(const char* p, usize n) noexcept : _buf{p, n} {}

  [[sfc_inline]] explicit Str(slice::Slice<const char> buf) noexcept : _buf{buf} {}

  [[sfc_inline]] static auto from_u8(slice::Slice<const char> v) -> Str {
    return Str{v};
  }

  [[sfc_inline]] auto as_ptr() const -> const char* {
    return _buf._ptr;
  }

  [[sfc_inline]] auto is_empty() const -> bool {
    return _buf._len == 0;
  }

  [[sfc_inline]] auto len() const -> usize {
    return _buf._len;
  }

  [[sfc_inline]] operator bool() const {
    return _buf._len != 0;
  }

  [[sfc_inline]] auto as_chars() const -> slice::Slice<const char> {
    return _buf;
  }

  [[sfc_inline]] auto as_bytes() const -> slice::Slice<const u8> {
    return {static_cast<const u8*>(static_cast<const void*>(_buf._ptr)), _buf._len};
  }

 public:
  [[sfc_inline]] auto get_unchecked(usize idx) const -> char {
    return _buf.get_unchecked(idx);
  }

  [[sfc_inline]] auto get_unchecked(ops::Range ids) const -> Str {
    return Str{_buf.get_unchecked(ids)};
  }

  [[sfc_inline]] auto operator[](usize idx) const -> char {
    return _buf[idx];
  }

  [[sfc_inline]] auto operator[](ops::Range ids) const -> Str {
    return Str{_buf[ids]};
  }

  [[sfc_inline]] auto split_at(usize mid) const -> tuple::Tuple<Str, Str> {
    const auto [a, b] = _buf.split_at(mid);
    return {Str{a}, Str{b}};
  }

  [[sfc_inline]] auto iter() const -> slice::Iter<const char> {
    return _buf.iter();
  }

 public:
  auto operator==(Str other) const -> bool {
    if (_buf._len != other.len()) {
      return false;
    }

    const auto ret = __builtin_memcmp(_buf._ptr, other.as_ptr(), _buf._len);
    return ret == 0;
  }

 public:
  auto find(auto p) const -> option::Option<usize>;
  auto rfind(auto p) const -> option::Option<usize>;

  auto contains(auto p) const -> bool;
  auto starts_with(auto p) const -> bool;
  auto ends_with(auto p) const -> bool;

  auto trim_start_matches(auto p) const -> Str;
  auto trim_end_matches(auto p) const -> Str;
  auto trim_matches(auto p) const -> Str;

  auto trim_start() const -> Str {
    return this->trim_start_matches(chr::is_whitespace);
  }

  auto trim_end() const -> Str {
    return this->trim_end_matches(chr::is_whitespace);
  }

  auto trim() const -> Str {
    return this->trim_matches(chr::is_whitespace);
  }

 public:
  void fmt(auto& f) const {
    f.pad(*this);
  }

  template <class T>
  auto parse() const -> option::Option<T>;
};

template <class T>
struct FromStr {
  static auto from_str(Str s) -> option::Option<T>;
};

template <class T>
auto Str::parse() const -> option::Option<T> {
  if constexpr (requires() { T::from_str(*this); }) {
    return T::from_str(*this);
  } else {
    return FromStr<T>::from_str(*this);
  }
}

}  // namespace sfc::str
